/*
 * Copyright 2012 Little Fluffy Toys Ltd Adapted from work by Reto Meier,
 * Copyright 2011 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package com.littlefluffytoys.littlefluffylocationlibrary;

import org.anize.ur.life.wimp.util.Util;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Binder;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.preference.PreferenceManager;

/**
 * This is an example of implementing an application service that will run in
 * response to an alarm, allowing us to move long duration work out of an intent
 * receiver.
 * 
 * @see AlarmService
 * @see AlarmService_Alarm
 */
public class LocationBroadcastService extends Service {

	private static final String TAG = "LocationBroadcastService";

	@Override
	public int onStartCommand(final Intent intent, final int flags,
			final int startId) {

		Util.d(TAG + ": onStartCommand");

		// Start up the thread running the service. Note that we create a
		// separate thread because the service normally runs in the process's
		// main thread, which we don't want to block.
		new Thread(null, mTask, TAG).start();
		// We want this service to continue running until it is explicitly
		// stopped, so return sticky.
		return START_STICKY;
	}

	@Override
	public void onDestroy() {

		Util.d(TAG + ": onDestroy");

	}

	/**
	 * The function that runs in our worker thread
	 */
	Runnable mTask = new Runnable() {
		@Override
		public void run() {
			boolean stopServiceOnCompletion = true;

			final SharedPreferences prefs = PreferenceManager
					.getDefaultSharedPreferences(LocationBroadcastService.this
							.getBaseContext());
			final long lastLocationUpdateTimestamp = prefs.getLong(
					LocationLibraryConstants.SP_KEY_LAST_LOCATION_UPDATE_TIME,
					0);
			final long lastLocationBroadcastTimestamp = prefs
					.getLong(
							LocationLibraryConstants.SP_KEY_LAST_LOCATION_BROADCAST_TIME,
							0);

			if (lastLocationBroadcastTimestamp == lastLocationUpdateTimestamp) {
				// no new location found

				Util.d(TAG + ": No new location update found");

				if ((System.currentTimeMillis() - lastLocationUpdateTimestamp) > LocationLibrary
						.getLocationMaximumAge()) {
					// Current location is out of date. Force an
					// update, and stop service if required.
					stopServiceOnCompletion = !forceLocationUpdate();
				}
			} else {
				final Editor prefsEditor = prefs.edit();
				prefsEditor
						.putLong(
								LocationLibraryConstants.SP_KEY_LAST_LOCATION_BROADCAST_TIME,
								lastLocationUpdateTimestamp);
				prefsEditor.commit();
				sendBroadcast(getBaseContext(), prefs, true);
			}

			if (stopServiceOnCompletion) {
				// Done with our work... stop the service!
				LocationBroadcastService.this.stopSelf();
			}
		}
	};

	protected static void sendBroadcast(final Context context,
			final SharedPreferences prefs, final boolean isPeriodicBroadcast) {
		final Intent locationIntent = new Intent(
				isPeriodicBroadcast ? LocationLibraryConstants.LOCATION_CHANGED_PERIODIC_BROADCAST_ACTION
						: LocationLibraryConstants.LOCATION_CHANGED_TICKER_BROADCAST_ACTION);
		final LocationInfo locationInfo = new LocationInfo(context);
		locationIntent.putExtra(
				LocationLibraryConstants.LOCATION_BROADCAST_EXTRA_LOCATIONINFO,
				locationInfo);
		if (LocationLibrary.showDebugOutput) {
			Util.d(TAG
					+ ": Broadcasting "
					+ (isPeriodicBroadcast ? "periodic" : "latest")
					+ " location update timed at "
					+ LocationInfo.formatTimeAndDay(
							prefs.getLong(
									LocationLibraryConstants.SP_KEY_LAST_LOCATION_UPDATE_TIME,
									System.currentTimeMillis()), true));
		}
		context.sendBroadcast(locationIntent,
				"android.permission.ACCESS_FINE_LOCATION");
	}

	@Override
	public IBinder onBind(final Intent intent) {
		return mBinder;
	}

	/**
	 * 
	 * @return true if the service should stay awake, false if not
	 */
	public boolean forceLocationUpdate() {
		final LocationManager locationManager = (LocationManager) getApplicationContext()
				.getSystemService(Context.LOCATION_SERVICE);
		final Criteria criteria = new Criteria();
		criteria.setAccuracy(Criteria.ACCURACY_COARSE);

		if (LocationLibraryConstants.SUPPORTS_GINGERBREAD) {
			if (LocationLibrary.showDebugOutput) {
				Util.d(TAG
						+ ": Force a single location update, as current location is beyond the oldest location permitted");
			}
			// just request a single update. The passive provider will pick it
			// up.
			final Intent receiver = new Intent(getApplicationContext(),
					PassiveLocationChangedReceiver.class)
					.addCategory(LocationLibraryConstants.INTENT_CATEGORY_ONE_SHOT_UPDATE);
			final PendingIntent oneshotReceiver = PendingIntent.getBroadcast(
					getApplicationContext(), 0, receiver,
					PendingIntent.FLAG_UPDATE_CURRENT);
			try {
				locationManager.requestSingleUpdate(criteria, oneshotReceiver);
			} catch (final IllegalArgumentException ex) {
				// thrown if there are no providers, e.g. GPS is off
				if (LocationLibrary.showDebugOutput) {
					Util.w(TAG
							+ ": IllegalArgumentException during call to locationManager.requestSingleUpdate - probable cause is that all location providers are off. Details: "
							+ ex.getMessage());
				}
			}
		} else { // pre-Gingerbread
			if (LocationLibrary.showDebugOutput) {
				Util.d(TAG
						+ ": Force location updates (pre-Gingerbread), as current location is beyond the oldest location permitted");
			}
			// one-shot not available pre-Gingerbread, so start updates, and
			// when one is received, stop updates.
			final String provider = locationManager.getBestProvider(criteria,
					true);
			if (provider != null) {
				locationManager.requestLocationUpdates(provider, 0, 0,
						preGingerbreadUpdatesListener,
						LocationBroadcastService.this.getMainLooper());
				// don't stop the service, the callback will do that
				return true;
			}
		}
		// stop the service
		return false;
	}

	/**
	 * Forces this service to be called after the given delay
	 */
	public static void forceDelayedServiceCall(final Context context,
			final int delayInSeconds, final Class<?> clazz) {

		final Intent serviceIntent = new Intent(context, clazz);

		final PendingIntent pIntent = PendingIntent
				.getService(
						context,
						LocationLibraryConstants.LOCATION_BROADCAST_REQUEST_CODE_SINGLE_SHOT,
						serviceIntent, PendingIntent.FLAG_ONE_SHOT);

		final AlarmManager alarm = (AlarmManager) context
				.getSystemService(Context.ALARM_SERVICE);
		alarm.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()
				+ (delayInSeconds * 1000), pIntent);
	}

	final LocationListener preGingerbreadUpdatesListener = new LocationListener() {
		@Override
		public void onLocationChanged(final Location location) {
			if (LocationLibrary.showDebugOutput) {
				Util.d(TAG + ": Single Location Update Received: "
						+ location.getLatitude() + ","
						+ location.getLongitude());
			}
			((LocationManager) getApplicationContext().getSystemService(
					Context.LOCATION_SERVICE))
					.removeUpdates(preGingerbreadUpdatesListener);

			if (!LocationLibraryConstants.SUPPORTS_FROYO) {
				// this
				// will not
				// be
				// broadcast
				// by the
				// passive
				// location
				// updater,
				// so we
				// will
				// process
				// it
				// ourselves
				PassiveLocationChangedReceiver.processLocation(
						LocationBroadcastService.this, location);
			}

			// Broadcast it
			// without
			// significant
			// delay.
			forceDelayedServiceCall(getApplicationContext(), 1,
					LocationBroadcastService.class);
			// Done with
			// our work...
			// stop the
			// service!
			LocationBroadcastService.this.stopSelf();
		}

		@Override
		public void onStatusChanged(final String provider, final int status,
				final Bundle extras) {
		}

		@Override
		public void onProviderEnabled(final String provider) {
		}

		@Override
		public void onProviderDisabled(final String provider) {
		}
	};

	/**
	 * This is the object that receives interactions from clients. See
	 * RemoteService for a more complete example.
	 */
	private final IBinder mBinder = new Binder() {
		@Override
		protected boolean onTransact(final int code, final Parcel data,
				final Parcel reply, final int flags) throws RemoteException {
			return super.onTransact(code, data, reply, flags);
		}
	};
}
